home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
BBS in a Box 3
/
BBS in a box - Trilogy III.iso
/
Files
/
Prog
/
B-C
/
C++Source Code Fmtr Folder
/
Src
/
Formatting.cp
< prev
next >
Encoding:
Amiga
Atari
Commodore
DOS
FM Towns/JPY
Macintosh
Macintosh JP
NeXTSTEP
RISC OS/Acorn
UTF-8
Wrap
Text File
|
1992-04-27
|
28.7 KB
|
1,258 lines
|
[
TEXT/MPS
]
#ifndef __CDENT__
#include "cdent.h"
#endif
#ifndef __CSCANNER__
#include "CScanner.h"
#endif
#ifndef __FORMATLOG__
#include "FormatLog.h"
#endif
#ifndef __FORMATTING__
#include "Formatting.h"
#endif
#ifndef __PARSERACTIONS__
#include "ParserActions.h"
#endif
#ifndef __CTYPE__
#include <ctype.h>
#endif
#ifndef __MEMORY__
#include <memory.h>
#endif
/*
** How to squeeze when lines exceed the output line length. The normal case
** is kSqueezeNone, which means that data are output exactly as they are seen
** without any other editorializing. The first other case is kSqueezeBlanks,
** which removes optional blanks. If this still doesn't scrunch the source,
** the final mechanism of kSqueezeConditionalNewLine is called into play, which
** interprets optional newlines as being mandatory newlines.
*/
enum {
kSqueezeNone, // Keep all formatting information
kSqueezeBlanks, // Remove optional blanks
kSqueezeUseSourceNewLines, // Use the original lines in the source
kSqueezeConditionalNewLine, // Use conditional newlines
kSqueezeEnd, // Last value, not used
kSqueezeFirst = kSqueezeBlanks // First value to try squeezing
};
//ƒ-
const short kDfltIndentDelta = 4;
const short kDfltWidth = 0;
const short kDfltIndent = 0;
const short kDfltTempIndent = 2;
const short kDfltTabSize = 4;
const short kDfltCommentColumn = 48;
const short kDfltLineLength = 120;
const Boolean kDfltReformatComments = false;
const Boolean kDfltSaveSourceNewLines = false;
const Boolean kDfltPassConsecutiveNewLines = true;
//ƒ+
/*
** Static class variables
*/
//ƒ-
DFile* Formatting::gOutput = 0;
FormatLog* Formatting::gFormatLog = 0;
short Formatting::gTabSize = kDfltTabSize;
short Formatting::gLineLength = kDfltLineLength;
short Formatting::gCommentColumn = kDfltCommentColumn;
Boolean Formatting::gDebug = false;
Boolean Formatting::gReformatComments = kDfltReformatComments;
Boolean Formatting::gPassSourceNewLines = kDfltSaveSourceNewLines;
Boolean Formatting::gPassConsecutiveNewLines = kDfltPassConsecutiveNewLines;
Boolean Formatting::gFromSource = true;
short Formatting::gSourceNewLines = 0;
Boolean Formatting::gLastNewLineFromSource = true;
//ƒ+
//ƒ-
struct RollbackVars Formatting::gV = {kSLex_NewLine, 0, true};
struct RollbackVars Formatting::gC;
size_t Formatting::gOutputOffset = 0;
static Boolean gConditionalNeedNewLine = false;
static FormatString gConditionalNewLineGlue = 0;
//ƒ+
//µ Formatting::IFormatting
#pragma segment Formatting
short Formatting::IFormatting()
{
short err = fSavedContexts.IDataArea();
if (err != noErr)
return (err);
SetComma(gFS_expr1);
SetSemi(gFS_stmt1);
SetLCurly(gFS_block1);
SetRCurly(gFS_block2);
SetLParen(gFS_expr5);
SetRParen(gFS_expr7);
SetName(gFS_name1);
SetOperator(gFS_expr4);
SetAssign(gFS_expr6);
SetRegister(kRegIndentDelta, kDfltIndentDelta);
SetRegister(kRegWidth, kDfltWidth);
SetRegister(kRegIndent, kDfltIndent);
SetRegister(kRegCurrentColumn, 0);
SetRegister(kRegTempIndent, kDfltTempIndent);
fContext.fDeclWidth = 0; // No minimum width for declarations
fContext.fDeclLeft = false; // "*" and "&" go right
SetWhatToSqueeze(kSqueezeNone); // Don't squeeze anything
// Safety measure. Drop a small context as the first one
fSavedContexts.Write(&fContext, sizeof(fContext));
fSavedContexts.DecrCursor(sizeof(fContext));
return (noErr);
}
// µ Formatting::IFormatting
#pragma segment Formatting
short Formatting::IFormatting(const Formatting *aFormat)
{
BlockMove(aFormat, this, sizeof(this));
return (fSavedContexts.IDataArea(&aFormat->fSavedContexts));
}
//µ Formatting::SetFormatLog
#pragma segment Formatting
void Formatting::SetFormatLog(FormatLog *aFormatLog)
{
gFormatLog = aFormatLog;
gFormatLog->Checkpoint(&fContext, &fSavedContexts, fSavedContexts.GetIndex(sizeof(fContext)));
gC = gV;
gOutputOffset = gOutput->GetCursor();
}
/*
** Make sure we are at the beginning of a new line
** Postcondition: LogicalNewLine()
*/
//µ Formatting::FreshLine
#pragma segment Formatting
void Formatting::FreshLine()
{
if (!IsBOL())
NewLine();
else
LogicalNewLine();
}
/*
** Move the cursor to the given column. If the cursor is past the column,
** leave the cursor where it is unless whitespace is requested.
*/
//µ Formatting::IndentTo
#pragma segment Formatting
void Formatting::IndentTo(short aColumn)
{
if (CurrentColumn() < aColumn) {
CurrentColumn() -= CurrentColumn() % gTabSize;// Make tabs work right
while (CurrentColumn() + gTabSize <= aColumn) {
gOutput->Putc('\t');
CurrentColumn() += gTabSize;
}
while (CurrentColumn() < aColumn) {
gOutput->Putc(' ');
CurrentColumn()++;
}
if (!IsBOL())
LogicalSpace();
}
}
/*
** Move the cursor to the indent column on a fresh line.
*/
//µ Formatting::IndentLine
#pragma segment Formatting
void Formatting::IndentLine(short aColumn)
{
FreshLine();
IndentTo(aColumn);
}
/*
** Format the comment as either an end of line type comment or as a line
** or block comment at the current indent level
*/
//µ Formatting::Comment
#pragma segment Formatting
void Formatting::Comment(const char *start, const char *end)
{
short commentColumn = (CurrentColumn() == 0) ? gV.fIndent : gCommentColumn;
/* Advance to the comment column */
IndentTo(commentColumn);
/*
** Comments to the end of line are not wrapped, they are just written
** as is. The comment will not be terminated by a newline: the newline
** appears as the next token
*/
if (start[1] == '/') {
_Write(start, end - start);
NeedNewLine(1);
return;
}
/*
** The comment is a standard C comment. Advance past the leading comment
** start and then display the comment. We save/restore gFromSource around
** these calls as the newlines that are desired should not flush buffers.
*/
Boolean saveFromSource = gFromSource;
gFromSource = false;
start += 2;
_Write("/*", 2);
if (true || !gReformatComments) {
Boolean twoStar = false; // lines prefaced by "**"
Boolean oneStar = false; // lines prefaced by " *"
while (start < end) {
switch (*start) {
case '\n':
// Advance to the first non-blank on the line. If there are
// any intervening newlines, emit them immediately.
start++;
while (isspace(*start)) {
if (*start == '\n')
NewLine();
start++;
}
// Indent to the comment column
NewLine();
IndentTo(commentColumn);
// Indent the body of the line. If the line starts with
// "**", no extra spaces are required. If the line
// starts with "*", then one extra space is required.
// If the line is the last line of a comment, then either
// zero or one extra space is required: zero when the
// previous lines started with zero or two "*", one when
// the previous lines began with a single star.
if (start[0] != '*') {
oneStar = false;
twoStar = false;
_Write(" ", 2);
} else if (start[1] == '*') {
oneStar = false;
twoStar = true;
} else {
if (start[1] != '/' || oneStar)
_Putc(' ');
oneStar = true;
twoStar = false;
}
_Putc(*start);
break;
default:
_Putc(*start);
break;
}
start++;
}
} else {
// Reformat the comment. Eventually
}
// Restore the saved value of gFromSource.
gFromSource = saveFromSource;
// Indicate that this was a newline
gV.fLastTokenType = kSLex_Comment;
}
/*
** Specify the glue to use for the next calls to display. The glue is
** immediately interpreted until the "•" character, which requires an item,
** is scanned. Because we are "greedy", there is no format left to
** consume so that only assignment is necessary.
*/
//µ Formatting::SetGlue
#pragma segment Formatting
void Formatting::SetGlue(FormatString aGlue)
{
gFormatLog->Record(Formatting::SetGlue, aGlue);
gV.fFormatString = aGlue;
gV.fGlueString = Interpret(aGlue);
if (gV.fGlueString == 0)
diag(kFatal, "Bad formatting string %s\n", aGlue);
}
/*
** Execute the glue without disturbing any other glue which might
** be in place.
*/
//µ Formatting::ExecuteGlue
#pragma segment Formatting
void Formatting::ExecuteGlue(FormatString aGlue)
{
gFormatLog->Record(Formatting::ExecuteGlue, aGlue);
if (Interpret(aGlue) == 0)
diag(kFatal, "Bad formatting string %s\n", aGlue);
}
/*
** Display the token. Remember what has been displayed and insert spaces as
** required
*/
//µ Formatting::Display
#pragma segment Formatting
void Formatting::Display(Syntactic *aToken)
{
Boolean tokenWritten = false;
if (aToken == 0)
return;
if (gDebug)
diag(kDebug, " # Display(%s)\n", Parser::NameOf(aToken->Type()));
switch (aToken->Type()) {
case kSLex_NewLine:
case kSPrs_NewLine:
{
Boolean emitNewLine = false;
// If the last newline emitted was a source newline and the current
// output line is empty, then this newline will be emitted. This
// lets us preserve line spacing that the user has provided.
if (gLastNewLineFromSource && CurrentColumn() == 0)
emitNewLine = true;
// If we always pass source newlines, then pass this one along also
if (gPassSourceNewLines)
emitNewLine = true;
// If a newline is needed, then this newline fits the bill so it
// should be emitted.
if (NewLineNeeded())
emitNewLine = true;
// If the last item emitted was a comment, then this newline should
// be emitted as it is most likely a newline at the end of a comment
if (gV.fLastTokenType == kSLex_Comment)
emitNewLine = true;
// Check if this is the second in a series of newlines. If it is, then
// we definitely want to emit this and the previous newline. Otherwise,
// remember that this newline is pending if this newline is not emitted
// this time around
if (gSourceNewLines > 0) {
tokenWritten = aToken->Display(this);
emitNewLine = true;
} else if (!emitNewLine && gPassConsecutiveNewLines)
gSourceNewLines = 1;
if (emitNewLine) {
tokenWritten = aToken->Display(this);
gLastNewLineFromSource = gFromSource;
gSourceNewLines = 0;
} else if (gFromSource) {
// If the newline is not being used, remember that it was here in
// case we have to reformat the line
gFormatLog->Record(Formatting::UseSourceNewLine);
}
// Remember that this token was written
if (tokenWritten)
gV.fLastTokenType = aToken->Type();
}
return;
case kSLex_Comment:
case kSLex_PoundLine:
// Comments are special. They are allowed to ignore the gV.fWantNewLine
// and gV.fWantSpace flags. However, gV.fNeedNewLine must be respected, along
// with emitting any pending newlines.
if (gSourceNewLines > 0) {
NewLine();
gSourceNewLines = 0;
}
while (NewLineNeeded())
NewLine();
tokenWritten = aToken->Display(this);
// Handle a preprocessor line. The MinorType() of the token describes
// which type of preprocessor line it is.
if (aToken->Type() == kSLex_PoundLine)
if (aToken->MinorType() == kSLex_PoundEndIf)
NeedNewLine(2);
else
NeedNewLine(1);
// Remember the type of this token if it was written
if (tokenWritten) {
gV.fLastTokenType = aToken->Type();
gFormatLog->Record(Formatting::Display, aToken);
}
return;
case kSLex_EOF:
// Emit any deferred newlines, both those from the source and those
// that were requested but not emitted
if (gSourceNewLines > 0) {
NewLine();
gSourceNewLines = 0;
}
while (NewLineWanted() || NewLineNeeded())
NewLine();
return;
}
/*
** Make sure required newlines and spaces have been emitted. Indent to
** the requested column. Display the token. Reset the requested indent.
** Update gV.fLastTokenType. Then if the glue was expecting a text string,
** interpret the remainder of the glue, advancing it. Note that this might
** modify gV.fLastTokenType if the glue contains a "!n" or "!s" within it.
** gSourceNewLines is set to 0 to indicate that any pending newlines were
** not emitted and have been ignored.
*/
CheckWanted();
IndentTo(gV.fIndent);
if (gV.fCurDeclStart < 0)
gV.fCurDeclStart = gV.fIndent;
tokenWritten = aToken->Display(this);
// Record this transaction in the FormatLog. Also remember the
// last token type for future adjacency checks
if (tokenWritten) {
gFormatLog->Record(Formatting::Display, aToken);
gV.fIndent = GetIndent();
gSourceNewLines = 0;
gV.fLastTokenType = aToken->Type();
if (gV.fLastTokenType == kSLex_Op) {
gV.fLastTokenType = aToken->MinorType();
if (gV.fLastTokenType == 0)
gV.fLastTokenType = kSLex_Op;
}
if (gV.fGlueString && *gV.fGlueString == (unsigned char)'•') {
gV.fGlueString = Interpret(++gV.fGlueString);
if (gV.fGlueString == 0)
diag(kFatal, "Bad formatting string %s\n", gV.fFormatString);
}
}
}
//µ Formatting::DeclPadStart
#pragma segment Formatting
void Formatting::DeclPadStart(int nOperators)
{
// If declaration operators go left, then there are no pad characters
if (DeclLeft())
return;
// Want at least one space separating the operators from the type
WantSpace();
// Compute the number of pad characters required.
if (WhatToSqueeze() != kSqueezeBlanks) {
short nPadChars = CurrentColumn() - gV.fCurDeclStart + nOperators;
while (nPadChars++ < fContext.fDeclWidth)
WantSpace();
}
}
//µ Formatting::DeclPadEnd
#pragma segment Formatting
void Formatting::DeclPadEnd()
{
// If declaration operators go right, then there are no pad characters
if (!DeclLeft())
return;
// Want at least one space separating the operators from the type
WantSpace();
// Compute the number of pad characters required.
if (WhatToSqueeze() != kSqueezeBlanks) {
short nPadChars = CurrentColumn() - gV.fCurDeclStart;
while (nPadChars++ < fContext.fDeclWidth)
WantSpace();
}
}
//µ Formatting::DeclWidth
#pragma segment Formatting
void Formatting::DeclWidth(Boolean goLeft, int aWidth)
{
fContext.fDeclLeft = goLeft;
fContext.fDeclWidth = aWidth;
gV.fCurDeclStart = -1;
}
/*
** Low level display routines
*/
//µ Formatting::Print
#pragma segment Formatting
void Formatting::Print(const char *aString)
{
if (aString)
Write(aString, strlen(aString));
}
//µ Formatting::Puts
#pragma segment Formatting
void Formatting::Puts(const char *aString)
{
gOutput->Puts(aString);
LogicalNewLine();
}
//µ Formatting::Putc
#pragma segment Formatting
void Formatting::Putc(int aChar)
{
if (aChar == '\n')
NewLine();
else {
_Putc(aChar);
if (isspace(aChar))
LogicalSpace();
}
}
//µ Formatting::NewLine
#pragma segment Formatting
void Formatting::NewLine()
{
// Check if the LineLength() has been exceeded. If it has, initiate
// the line shortening procedures. (This is a test, it is only a test)
if (gDebug)
diag(kDebug, " # \\n\n");
// Emit the newline to standard out. Note that a newline has been emitted.
// Indicate that the last newline was not from source. The line has been
// emitted, so reset
gOutput->Putc('\n');
LogicalNewLine();
gLastNewLineFromSource = false;
// Checkpoint the world if required. Flush the buffer if the newline came
// from source. We don't flush when not coming from source because the
// newline *might* be provisional and undone when retrying.
if (gFromSource) {
gOutput->Flush(4096);
gFormatLog->Checkpoint(&fContext, &fSavedContexts, fSavedContexts.GetIndex(sizeof(fContext)));
gC = gV;
gOutputOffset = gOutput->GetCursor();
}
}
//µ Formatting::Write
#pragma segment Formatting
void Formatting::Write(const void *aString, size_t n)
{
gOutput->Write(aString, n);
const char *p = (const char *)aString;
while (n--) {
char aChar = *p++;
if (aChar == '\n')
LogicalNewLine();
else if (isspace(aChar))
LogicalSpace();
else
CurrentColumn()++;
}
}
//µ Formatting::LogicalSpace
#pragma segment Formatting
void Formatting::LogicalSpace()
{
if (SpaceWanted())
--gV.fWantSpace;
if (SpaceNeeded())
--gV.fNeedSpace;
gV.fLastTokenType = kSLex_Null;
}
//µ Formatting::LogicalNewLine
#pragma segment Formatting
void Formatting::LogicalNewLine()
{
CurrentColumn() = 0;
if (NewLineWanted())
--gV.fWantNewLine;
if (NewLineNeeded())
--gV.fNeedNewLine;
gV.fWantSpace = 0;
gV.fNeedSpace = 0;
AtBOL();
}
//µ Formatting::OpenContext
#pragma segment Formatting
void Formatting::OpenContext(Boolean isGroup)
{
int aDepth = fSavedContexts.GetIndex(sizeof(fContext));
gFormatLog->RecordDepth(aDepth);
gFormatLog->Record(Formatting::OpenContext, isGroup);
fSavedContexts.Write(&fContext, sizeof(fContext));
fContext.fIsGroup = isGroup;
if (gDebug)
diag(kDebug, " # OpenContext(%d), SP = %d\n", isGroup, aDepth);
}
//µ Formatting::CloseContext
#pragma segment Formatting
void Formatting::CloseContext()
{
int aDepth = fSavedContexts.GetIndex(sizeof(fContext)) - 1;
gFormatLog->RecordDepth(aDepth);
gFormatLog->Record(Formatting::CloseContext);
// Restore the previous context. Preserve the current column as that does
// is a dynamic value.
short aColumn = CurrentColumn();
fSavedContexts.DecrCursor(sizeof(fContext));
fContext = *(FContext *)fSavedContexts.GetData();
CurrentColumn() = aColumn;
gV.fIndent = GetIndent();
if (gDebug)
diag(kDebug, " # CloseContext, SP = %d\n", aDepth);
}
//µ Formatting::RestoreIndent
#pragma segment Formatting
void Formatting::RestoreIndent()
{
gFormatLog->Record(Formatting::RestoreIndent);
FContext * aContext = (FContext *)fSavedContexts.GetData(fSavedContexts.GetCursor() - sizeof(fContext));
fContext.fReg[kRegIndentDelta] = aContext->fReg[kRegIndentDelta];
fContext.fReg[kRegWidth] = aContext->fReg[kRegWidth];
fContext.fReg[kRegIndent] = aContext->fReg[kRegIndent];
fContext.fReg[kRegTempIndent] = aContext->fReg[kRegTempIndent];
gV.fIndent = GetIndent();
}
/*
** Postcondition: LogicalSpace() || LogicalNewLine()
*/
//µ Formatting::CheckWanted
#pragma segment Formatting
void Formatting::CheckWanted()
{
// Do newlines first. This can turn off the need for spaces
while (NewLineWanted() || NewLineNeeded())
NewLine();
// Emit spaces after newlines.
while (SpaceWanted() || SpaceNeeded()) {
_Putc(' ');
LogicalSpace();
}
// Finally check if we need a newline.
if (CurrentColumn() >= LineLength()) {
// If we are in the middle of a Rollback(), then don't Rollback() again.
if (gFromSource) {
// Indicate that text is no longer from source.
gFromSource = false;
// Iterate over the squeeze options that have not been applied
// until the current column is less than the line length at the
// end of the squeeze. Note that the squeeze option is propagated
// up the context stack
short sqType = WhatToSqueeze();
// Squeeze from the first choice to the last
if (sqType < kSqueezeFirst)
sqType = kSqueezeFirst;
while (sqType < kSqueezeEnd && CurrentColumn() > LineLength()) {
if (gDebug)
diag(kDebug, " #\n # ——> Rollback starting. SetSqueeze(%d)\n", sqType);
// Rollback the log, the global variables, and the beginning of
// the line
gFormatLog->Rollback(&fContext, &fSavedContexts);
gV = gC;
gOutput->SetCursor(gOutputOffset);
// Start squeezing.
SetSqueeze(sqType);
// Redo all the saved items
while (gFormatLog->Redo(this))
;
if (gDebug)
diag(kDebug, " # <—— Rollback completed\n #\n");
sqType++;
}
// Done redoing. Allow redo to work.
SetSqueeze(kSqueezeNone);
gFormatLog->EnableRecord();
gFromSource = true;
// If still beyond the end of the line, just put out a newline
if (CurrentColumn() >= LineLength())
NewLine();
}
}
}
//µ Formatting::SetSqueeze
#pragma segment Formatting
void Formatting::SetSqueeze(short whatToSqueeze)
{
int minDepth = gFormatLog->MinDepth();
int maxDepth = gFormatLog->MaxDepth();
SetWhatToSqueeze(whatToSqueeze);
while (minDepth <= maxDepth) {
((FContext *)fSavedContexts.GetData(minDepth, sizeof(fContext)))->fWhatToSqueeze = whatToSqueeze;
++minDepth;
}
gConditionalNeedNewLine = (whatToSqueeze == kSqueezeConditionalNewLine);
gConditionalNewLineGlue = 0;
}
//µ Formatting::UseSourceNewLine
#pragma segment Formatting
void Formatting::UseSourceNewLine()
{
if (WhatToSqueeze() == kSqueezeUseSourceNewLines)
NeedNewLine();
}
/*µ - FormatString interpreters
*/
/*µ consumeWhiteSpace
** Return a pointer to the first non-whitespace character in the FormatString
*/
static FormatString consumeWhiteSpace(register FormatString aFormat)
{
if (aFormat)
while (isspace(*aFormat))
++aFormat;
return (aFormat);
}
/*µ consumeBalancedParens
** Assure aFormat points at an open parenthesis, then advance it past
** all balanced pairs of parentheses. Return 0 if an error occurred,
** otherwise return the pointer to the first character past the closing
** parenthesis.
*/
static FormatString consumeBalancedParens(register FormatString aFormat)
{
aFormat = consumeWhiteSpace(aFormat);
if (aFormat && *aFormat == '{') {
int parenCount = 0;
while (*aFormat) {
switch (*aFormat++) {
case '{':
++parenCount;
break;
case '}':
if (--parenCount == 0)
return (consumeWhiteSpace(aFormat));
break;
case '\'':
aFormat++;
break;
}
}
}
return (0);
}
//µ Formatting::Interpret
#pragma segment Formatting
FormatString Formatting::Interpret(register FormatString aFormat)
{
short n;
Boolean aBool;
while (aFormat && *aFormat) {
switch (*aFormat++) {
case 'i': // Set the indent
SetRegister(kRegIndent, aFormat);
gV.fIndent = GetIndent();
SetRegister(kRegTempIndent, gV.fIndent);
if (gDebug)
diag(kDebug, " # i%d\n", GetIndent());
break;
case 't': // Set the temporary indent
SetRegister(kRegTempIndent, aFormat);
if (gDebug)
diag(kDebug, " # t%d\n", GetRegister(kRegTempIndent));
break;
case 'c': // Set the next column
gV.fIndent = GetExpr(aFormat);
if (gDebug)
diag(kDebug, " # c%d\n", gV.fIndent);
break;
case '/': // Conditional break
{
Boolean didNewLine = false;
if (*aFormat == '+')
SetRegister(kRegTempIndent, aFormat);
if (gConditionalNeedNewLine && (gConditionalNewLineGlue == 0 || gConditionalNewLineGlue == aFormat)) {
didNewLine = true;
gConditionalNewLineGlue = aFormat;
NeedNewLine();
}
if (gDebug)
diag(kDebug, " # / (%d)\n", didNewLine);
}
break;
case 'd': // Declaration width
aBool = (*aFormat++ == 'l');
n = GetExpr(aFormat);
DeclWidth(aBool, n);
break;
case 's': // Emit blanks
aBool = (*aFormat == '#');
if (aBool)
++aFormat;
n = GetExpr(aFormat);
if (aBool) {
if (WhatToSqueeze() != kSqueezeBlanks)
WantSpace(n);
} else
NeedSpace(n);
if (gDebug)
diag(kDebug, " # s%s%d\n", (aBool ? "#" : ""), n);
break;
case 'n': // Want new lines
aBool = (*aFormat == '#');
if (aBool)
++aFormat;
n = GetExpr(aFormat);
if (aBool)
WantNewLine(n);
else
NeedNewLine(n);
if (gDebug)
diag(kDebug, " # n%s%d\n", (aBool ? "#" : ""), n);
break;
case '&': // Require
switch (*aFormat++) {
case 'n': // …beginning of line
aBool = (!IsBOL() && !NewLineWanted() && !NewLineNeeded());
if (aBool)
NeedNewLine();
if (gDebug)
diag(kDebug, " # &n(%d)\n", aBool);
break;
case 's': // …last was space
aBool = (!IsBOL() && gV.fLastTokenType != kSLex_Null && !SpaceWanted() && !SpaceNeeded());
if (aBool)
NeedSpace();
if (gDebug)
diag(kDebug, " # &s(%d)\n", aBool);
break;
default:
return (0);
}
break;
case '!': // Assert…
switch (*aFormat++) {
case 'n': // …beginning of line
if (!NewLineWanted() && !NewLineNeeded())
AtBOL();
else {
if (NewLineWanted())
gV.fWantNewLine--;
if (NewLineNeeded())
gV.fNeedNewLine--;
}
if (gDebug)
diag(kDebug, " # !n\n");
break;
case 's': // …last was space
if (!SpaceWanted() && !SpaceNeeded())
gV.fLastTokenType = kSLex_Null;
else {
if (SpaceWanted())
gV.fWantSpace--;
if (SpaceNeeded())
gV.fNeedSpace--;
}
if (gDebug)
diag(kDebug, " # !s\n");
break;
default:
return (0);
}
break;
case '?': // Conditional…
switch (*aFormat++) {
case 'n': // …beginning of line
aBool = IsBOL();
break;
case 'i': // …last was id or value
aBool = IsIdentifierType(gV.fLastTokenType);
break;
case 'o': // …last was operator
aBool = IsOperatorType(gV.fLastTokenType);
break;
case '\'':
switch (*aFormat++) {
case '(': // …last was left paren
aBool = (gV.fLastTokenType == kSLex_LParen);
break;
case ')': // …last was right paren
aBool = (gV.fLastTokenType == kSLex_RParen);
break;
case '{': // …last was left curly
aBool = (gV.fLastTokenType == kSLex_LCurly);
break;
case '}': // …last was right curly
aBool = (gV.fLastTokenType == kSLex_RCurly);
break;
case ';': // …last was operator
aBool = (gV.fLastTokenType == kSLex_SemiColon);
break;
default: // …error
return (0);
}
break;
default: // …error
return (0);
}
if (gDebug)
diag(kDebug, " # ?%c == %d\n", aFormat[-1], aBool);
if (!aBool)
aFormat = consumeBalancedParens(aFormat);
if (aFormat == 0 || *aFormat++ != '{')
return (0);
break;
case '=': // Assign to a register
if (!isdigit(*aFormat))
return (0);
n = *aFormat++ - '0';
SetRegister(n, aFormat);
if (gDebug)
diag(kDebug, " # =%d %d\n", n, GetRegister(n));
break;
case ((unsigned char)'•'): // Display an item
if (gDebug)
diag(kDebug, " # •\n");
return (aFormat - 1);
case ' ': // Ignore whitespace
case '\t':
break;
case '{': // Ignore it
aFormat = consumeBalancedParens(aFormat - 1);
break;
case '}': // Skip over it
break;
default: // Unknown character.
return (aFormat - 1);
}
}
return (aFormat);
}
/*
** Set the register to the value of the expression in the string
*/
//µ Formatting::SetRegister
#pragma segment Formatting
void Formatting::SetRegister(int aRegister, FormatString &aFormat)
{
int op;
switch (*aFormat) {
case '+':
case '-':
op = *aFormat++;
break;
default:
op = '=';
break;
}
int expr = GetExpr(aFormat);
if (aFormat != 0) {
switch (op) {
case '+':
SetRegister(aRegister, GetRegister(aRegister) + expr);
break;
case '-':
SetRegister(aRegister, GetRegister(aRegister) - expr);
break;
case '=':
SetRegister(aRegister, expr);
break;
}
}
}
/*
** Evaluate the expression. Return the value and advance aFormat past the
** expression. If there was an error, aFormat == 0. If there was no
** expression, return the default value.
*/
//µ Formatting::GetExpr
#pragma segment Formatting
int Formatting::GetExpr(FormatString &aFormat, int defaultValue)
{
int sum = defaultValue;
int op = '=';
Boolean done = false;
while (!done && aFormat && *aFormat) {
int term;
Boolean haveTerm = false;
switch (*aFormat) {
case '+': // Add to sum
case '-': // Subtract from sum
// Remember operation, get operand.
op = *aFormat++;
break;
case ((unsigned char)'®'): // Register reference
if (!isdigit(*++aFormat)) {
aFormat = 0;
return (0);
}
term = GetRegister(*aFormat++ - '0');
haveTerm = true;
break;
case ' ': // Ignore whitespace
case '\t':
++aFormat;
break;
default: // Number
if (isdigit(*aFormat)) {
term = 0;
while (isdigit(*aFormat)) {
term *= 10;
term += *aFormat++ - '0';
}
haveTerm = true;
} else
done = true;
break;
}
// Here after having scanned a term. op is the operation.
if (haveTerm)
switch (op) {
case '=':
sum = term;
break;
case '+':
sum += term;
break;
case '-':
sum -= term;
break;
}
}
return (sum);
}
/*
** Return true if the last token was a beginning of line token type
*/
//µ Formatting::IsBOL
#pragma segment Formatting
Boolean Formatting::IsBOL()
{
return (gV.fLastTokenType == kSLex_NewLine || gV.fLastTokenType == kSPrs_NewLine);
}
/*
** Return true if the type is one which should be treated as an identifier
** for formatting purposes. This class subsumes reserved words and values
** in addition to identifiers as these may not be placed adjacent to each
** other with impunity
*/
//µ Formatting::IsIdentifierType
#pragma segment Formatting
Boolean Formatting::IsIdentifierType(short aType)
{
switch (aType) {
case kSLex_Id:
case kSLex_ParsedId:
case kSLex_Value:
case kSPrs_Id:
case kSPrs_DeclOperator:
return (true);
default:
return (aType >= kSLex_ReservedWordFirst && aType <= kSLex_ReservedWordLast);
}
}
/*
** Return true if the type is that belonging to an operator
*/
//µ Formatting::IsOperatorType
#pragma segment Formatting
Boolean Formatting::IsOperatorType(short aType)
{
return (aType >= kSLex_OpFirst && aType <= kSLex_OpLast);
}